<template>
  <div class="flyparameter">
    <div class="toolbar">
      <el-page-header :show-back="showBack" @back="back">
        <template #content>
          <span class="inline-block" v-ellipsis="500">{{
            $t("flyparam.app_name") + `: ${innerAppName}`
          }}</span></template
        >
      </el-page-header>
      <div class="toolbar--right">
        <el-dropdown
          v-permission="{ permission: PERMISSIONS.DESIGN }"
          :split-button="mode === 1"
          :class="{ 'save-dropdown': mode === 1 }"
          @click="saveAll"
        >
          <svg-icon
            :class="{ 'mr-5': mode === 2 }"
            icon-class="save"
            class="text-lg icon-save toolbar-icon"
            @click="
              () => {
                mode === 2 && saveAll();
              }
            "
          />
          <template #dropdown>
            <el-dropdown-menu v-if="mode === 1">
              <el-dropdown-item
                v-for="tab in tabs.filter((item) => item.tableType !== 3)"
                :key="tab.id"
                :disabled="!hasChanged(tab)"
              >
                <div class="flex items-center" @click="() => save(tab.id)">
                  {{ tab.name }}
                </div>
              </el-dropdown-item>
            </el-dropdown-menu></template
          >
        </el-dropdown>
        <el-dropdown class="mr-5" @command="handlePreviewCommand">
          <span class="el-dropdown-link">
            <svg-icon icon-class="eye-open" class="text-lg icon-eye toolbar-icon" />
          </span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item command="dataPreview">
                <div class="flex items-center">
                  <svg-icon icon-class="file-zip" class-name="mr-[5]" />{{
                    $t("flyparam.data_preview")
                  }}
                </div>
              </el-dropdown-item>
              <el-dropdown-item command="confPagePreview">
                <div class="flex items-center">
                  <svg-icon icon-class="web-page" class-name="mr-[5]" />{{
                    $t("flyparam.page_preview")
                  }}
                </div>
              </el-dropdown-item>
            </el-dropdown-menu></template
          >
        </el-dropdown>
        <el-dropdown
          class="mr-5"
          :disabled="!hasDesignPermission"
          @command="handleToolbarCommand"
        >
          <span class="el-dropdown-link">
            <i class="el-icon-s-tools" />
          </span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item
                v-permission="{ permission: PERMISSIONS.DESIGN }"
                command="undo"
              >
                <div class="flex items-center">
                  <svg-icon icon-class="revoke" class-name="mr-[5]" />{{
                    $t("flyparam.undo")
                  }}
                </div>
              </el-dropdown-item>
              <el-dropdown-item
                v-if="!disableSettings"
                v-permission="{ permission: PERMISSIONS.DESIGN }"
                command="redesign"
              >
                <div class="flex items-center">
                  <i class="el-icon-delete" />
                  {{ $t("flyparam.redesign") }}
                </div>
              </el-dropdown-item>
              <el-dropdown-item v-if="!disableSettings" command="import/export">
                <div class="flex items-center">
                  <svg-icon
                    icon-class="import-export"
                    style="width: 20px; height: 20px"
                  />{{ $t("common.import") }} / {{ $t("common.export") }}
                </div>
              </el-dropdown-item>
            </el-dropdown-menu></template
          >
        </el-dropdown>
        <!-- <span class="inline-block align-middle el-dropdown-link">
          <svg-icon icon-class="question3" class-name="text-lg toolbar-icon" />
        </span> -->
      </div>
    </div>
    <el-row type="flex" class="flyparameter-design-row">
      <el-col v-if="mode === 1" :span="4">
        <div class="sidebar" style="height: calc(100vh - 104px)">
          <div class="sidebar-table" style="height: 50%">
            <div class="sidebar-title">
              <span>{{ $t("flyparam.table") }}</span>
            </div>
            <div class="sidebar-body">
              <el-tree
                ref="tableTree"
                :data="tables"
                node-key="id"
                :expand-on-click-node="false"
                default-expand-all
                :props="defaultProps"
                @node-click="newTab"
              >
                <template v-slot="{ node, data }">
                  <div class="w-full">
                    <div
                      class="flex justify-between items-center w-full pr-3"
                      :class="{
                        'tree-node-active': currentTab && data.id === currentTab.id,
                      }"
                    >
                      <span v-ellipsis class="flex items-center" style="width: 230px">
                        <svg-icon
                          :icon-class="_treeIconClass(data)"
                          class-name="mr-2"
                          @click="expandTreeNode(data.id)"
                        />
                        {{ data.name }}
                      </span>
                      <i
                        v-if="data.children"
                        v-permission="{ permission: PERMISSIONS.DESIGN }"
                        class="el-icon-plus icon-plus"
                        @click.stop="() => (newTableVisible = true)"
                      />
                      <el-dropdown
                        v-else
                        v-permission="{ permission: PERMISSIONS.DESIGN }"
                        placement="bottom-start"
                        @command="(command) => handleTableDropdownCommand(command, data)"
                      >
                        <svg-icon icon-class="menu_3" />
                        <template #dropdown>
                          <el-dropdown-menu
                            v-permission="{ permission: PERMISSIONS.DESIGN }"
                          >
                            <el-dropdown-item command="delete" icon="el-icon-delete">
                              <span>{{ $t("common.delete") }}</span>
                            </el-dropdown-item>
                            <el-dropdown-item command="rename" icon="el-icon-edit">
                              <span>{{ $t("flyparam.rename") }}</span>
                            </el-dropdown-item>
                          </el-dropdown-menu></template
                        >
                      </el-dropdown>
                    </div>
                  </div>
                </template>
              </el-tree>
            </div>
          </div>
          <div class="sidebar-components" style="height: 50%">
            <div class="sidebar-title">
              <span>{{ $t("flyparam.components") }}</span>
            </div>
            <div class="sidebar-body">
              <el-dropdown
                v-if="activeComponentTab === 'custom'"
                v-permission="{ permission: PERMISSIONS.DESIGN }"
                class="components-dropdown"
                @command="handleCommand"
              >
                <span class="inline-block">
                  <svg-icon icon-class="menu" class-name="components-menu" />
                </span>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item command="new" icon="el-icon-plus">
                      <span>{{ $t("flyparam.new_component") }}</span>
                    </el-dropdown-item>
                    <el-dropdown-item command="import/export">
                      <svg-icon
                        icon-class="import-export"
                        style="width: 22px; height: 22px; margin-left: -3px"
                      />
                      <span>{{ $t("common.import") }} / {{ $t("common.export") }}</span>
                    </el-dropdown-item>
                  </el-dropdown-menu></template
                >
              </el-dropdown>

              <el-tabs v-model="activeComponentTab">
                <el-tab-pane :label="$t('flyparam.basic')" name="basic">
                  <ul class="basic-components">
                    <li
                      v-for="item in basicComponents"
                      :key="item.id"
                      @drag="(e) => drag(e, item)"
                      @dragend="(e) => dragend(e, item)"
                    >
                      <span class="basic-component__label">
                        <img
                          v-if="item.valueType === types.STRING"
                          src="@/icons/svg/parameter/string.png"
                          alt
                        />
                        <img
                          v-else-if="item.valueType === types.BOOLEAN"
                          src="@/icons/svg/parameter/boolean.png"
                          alt
                        />
                        <img
                          v-else-if="item.valueType === types.NUMBER"
                          src="@/icons/svg/parameter/number.png"
                          alt
                        />
                        <img
                          v-else-if="item.valueType === types.HEX"
                          src="@/icons/svg/parameter/hex.png"
                          alt
                        />
                        <img
                          v-else-if="item.valueType === types.FILE"
                          src="@/icons/svg/parameter/file.png"
                          alt
                        />
                        <img
                          v-else-if="item.valueType === types.REFERENCE"
                          src="@/icons/svg/parameter/link.png"
                          alt
                        />
                        <img
                          v-else-if="item.type === 2"
                          src="@/icons/svg/parameter/divider.png"
                          style="width: 18px"
                          alt
                        />
                        <i
                          v-else-if="item.valueType === types.TIME"
                          style="font-size: 1.2em"
                          class="el-icon-alarm-clock"
                        />
                        <i
                          v-else-if="item.valueType === types.DATE"
                          style="font-size: 1.2em"
                          class="el-icon-date"
                        />
                        <i v-else class="el-icon-time" style="font-size: 1.2em" />
                        {{ item.label }}
                      </span>
                      <i
                        v-permission="{ permission: PERMISSIONS.DESIGN }"
                        class="el-icon-plus icon-plus"
                        @click="() => addItem(item)"
                      />
                    </li>
                  </ul>
                </el-tab-pane>
                <el-tab-pane :label="$t('flyparam.custom')" name="custom">
                  <el-tree :data="commonComponents" default-expand-all>
                    <template v-slot="{ node, data }">
                      <div class="w-full">
                        <div class="flex justify-between items-center w-full pr-3">
                          <span v-ellipsis class="inline-block" style="width: 160px">
                            <template v-if="data.children">
                              <img src="@/icons/svg/parameter/category.png" alt />
                            </template>
                            <template v-else>
                              <img
                                v-if="data.valueType === types.STRING"
                                src="@/icons/svg/parameter/string.png"
                                alt
                              />
                              <img
                                v-else-if="data.valueType === types.BOOLEAN"
                                src="@/icons/svg/parameter/boolean.png"
                                alt
                              />
                              <img
                                v-else-if="data.valueType === types.NUMBER"
                                src="@/icons/svg/parameter/number.png"
                                alt
                              />
                              <img
                                v-else-if="data.valueType === types.HEX"
                                src="@/icons/svg/parameter/hex.png"
                                alt
                              />
                              <img
                                v-else-if="data.valueType === types.FILE"
                                src="@/icons/svg/parameter/file.png"
                                alt
                              />
                              <img
                                v-else-if="data.valueType === types.REFERENCE"
                                src="@/icons/svg/parameter/link.png"
                                alt
                              />
                            </template>
                            {{ node.label }}
                          </span>
                          <div
                            v-permission="{ permission: PERMISSIONS.DESIGN }"
                            class="flex items-center"
                          >
                            <i
                              v-if="!data.children"
                              class="el-icon-plus icon-plus"
                              @click.stop="() => addItem(data)"
                            />
                            <div class="inline-block">
                              <el-dropdown
                                v-if="!data.children"
                                @command="
                                  (command) => handleComponentCommand(command, data)
                                "
                              >
                                <span class="inline-block">
                                  <svg-icon
                                    icon-class="menu"
                                    class-name="components-menu"
                                  />
                                </span>
                                <template #dropdown>
                                  <el-dropdown-menu>
                                    <el-dropdown-item command="edit" icon="el-icon-edit">
                                      <span>{{ $t("common.edit") }}</span>
                                    </el-dropdown-item>
                                    <el-dropdown-item
                                      command="remove"
                                      icon="el-icon-delete"
                                    >
                                      <span>{{ $t("common.delete") }}</span>
                                    </el-dropdown-item>
                                  </el-dropdown-menu></template
                                >
                              </el-dropdown>
                            </div>
                          </div>
                        </div>
                      </div>
                    </template>
                  </el-tree>
                </el-tab-pane>
              </el-tabs>
            </div>
          </div>
        </div>
      </el-col>
      <el-col v-loading="layoutLoading" :span="mode === 1 ? 20 : 24" class="main-content">
        <div v-show="currentLayout" class="param-button-group">
          <button
            v-if="tableType !== 3"
            :class="{ active: page === 'Design' }"
            @click="() => (page = 'Design')"
          >
            {{ $t("flyparam.tab_design") }}
          </button>
          <button
            v-if="tableType !== 3"
            :class="{ active: page === 'ParamList' }"
            @click="() => (page = 'ParamList')"
          >
            {{ $t("flyparam.tab_param") }}
          </button>
          <button
            v-if="!isProfile && tableType !== 1"
            :class="{ active: page === 'EntryList' }"
            @click="handleEntryListOnClick"
          >
            {{ $t("flyparam.tab_entity") }}
          </button>
        </div>
        <el-tabs
          v-model="currentTabId"
          :addable="false"
          type="border-card"
          @tab-click="handleTabClick"
          @tab-remove="handleTabRemove"
        >
          <el-tab-pane
            v-for="tab in tabs"
            :key="tab.id"
            :label="tabLabel(tab)"
            :name="tab.id"
            :closable="tab.name !== 'Profile'"
          >
            <el-card
              shadow="never"
              style="margin: 10px 0 0 0"
              class="parameter-layout"
              :body-style="{ padding: '0px' }"
            >
              <keep-alive>
                <el-row v-if="page === 'Design'" type="flex">
                  <el-col id="parameter-panel" :span="18" class="main-content__left">
                    <div id="content" :style="{ height: contentHeight }">
                      <el-form
                        :id="tab.id"
                        ref="layoutForm"
                        :model="tab.form"
                        @submit.native.prevent
                      >
                        <grid-layout
                          :key="tab.randomKey"
                          ref="gridlayout"
                          :layout.sync="tab.data"
                          :col-num="colNumber"
                          :row-height="32"
                          :is-draggable="true"
                          :is-mirrored="false"
                          :is-bounded="true"
                          :margin="[5, 10]"
                          :vertical-compact="true"
                          :use-css-transforms="true"
                          @layout-ready="layoutReadyEvent"
                          @layout-updated="layoutUpdatedEvent"
                        >
                          <grid-item
                            v-for="item in tab.data"
                            :key="item.i"
                            drag-ignore-from="input, .el-input__inner, .el-textarea__inner, .el-select, .el-input, .parameter-reference, .el-checkbox, .parameter-boolean"
                            style="touch-action: none"
                            :static="item.i === 'new'"
                            :x="item.x"
                            :y="item.y"
                            :w="item.w"
                            :h="item.h"
                            :i="item.i"
                          >
                            <div
                              v-if="item.i === 'new'"
                              class="parameter-card text-center new-param"
                              @click="addItem"
                            >
                              <span class="inline-block" style="line-height: 62px">{{
                                $t("flyparam.new_param")
                              }}</span>
                            </div>
                            <div v-else-if="item.i === 'drop'">
                              <span class="inline-block" style="line-height: 62px" />
                            </div>
                            <parameter
                              v-else
                              has-border
                              :reference-disabled="refTableType !== 2"
                              design
                              :disabled="!hasDesignPermission"
                              :item="item"
                              :form="tab.form"
                              :active-id="activeId"
                              :template-id="templateId"
                              @contextmenu.prevent.native="
                                handleContextMenu($event, item.i)
                              "
                              @click.native="() => handleParameterClick(item)"
                              @close="removeItem"
                              @select-file="(id) => handleSelectFile(id)"
                              @select-reference="(id) => handleSelectReference(id)"
                              @newTab="(id) => handleReferenceTableClick(id)"
                            />
                          </grid-item>
                        </grid-layout>
                      </el-form>
                    </div>
                  </el-col>
                  <!--Param Properties -->
                  <el-col
                    v-loading="paramPropertiesLoading"
                    :span="6"
                    class="param-properties"
                  >
                    <div class="param-properties__title flex justify-between">
                      <span>{{ $t("flyparam.properties") }}</span>
                    </div>
                    <div class="main-content__right">
                      <el-form
                        v-if="currentItem"
                        ref="propertiesForm"
                        :disabled="!hasDesignPermission"
                        size="small"
                        :rules="rules"
                        :model="currentItem"
                        @submit.native.prevent
                      >
                        <div v-if="currentItem.type === 2">
                          <el-form-item :label="$t('flyparam.title')" prop="title">
                            <el-input
                              v-model="currentItem.title"
                              size="small"
                              @blur="() => (currentItem.title = currentItem.title.trim())"
                            />
                          </el-form-item>
                          <el-form-item
                            :label="$t('flyparam.align')"
                            prop="horizontalAlign"
                          >
                            <el-radio-group v-model="currentItem.horizontalAlign">
                              <el-radio label="l">{{
                                $t("flyparam.align_left")
                              }}</el-radio>
                              <el-radio label="c">{{
                                $t("flyparam.align_center")
                              }}</el-radio>
                              <el-radio label="r">{{
                                $t("flyparam.align_right")
                              }}</el-radio>
                            </el-radio-group>
                          </el-form-item>
                        </div>
                        <div v-else>
                          <el-form-item :label="$t('flyparam.param_label')" prop="label">
                            <el-input
                              v-model="currentItem.label"
                              size="small"
                              :maxlength="64"
                              @blur="() => (currentItem.label = currentItem.label.trim())"
                            />
                          </el-form-item>
                          <el-form-item :label="$t('flyparam.param_key')" prop="key">
                            <el-input
                              v-model="currentItem.key"
                              size="small"
                              :maxlength="64"
                              @blur="() => (currentItem.key = currentItem.key.trim())"
                            />
                          </el-form-item>
                          <el-form-item
                            :label="$t('flyparam.param_type')"
                            prop="valueType"
                          >
                            <el-select
                              v-model="currentItem.valueType"
                              :disabled="!currentItem.new"
                              size="small"
                              @change="handleValueTypeChange"
                            >
                              <el-option label="String" :value="types.STRING" />
                              <el-option label="Number" :value="types.NUMBER" />
                              <el-option label="Hex" :value="types.HEX" />
                              <el-option label="Boolean" :value="types.BOOLEAN" />
                              <el-option label="File" :value="types.FILE" />
                              <el-option
                                v-if="mode === 1"
                                label="Reference"
                                :value="types.REFERENCE"
                              />
                              <el-option label="Time" :value="types.TIME" />
                              <el-option label="Date" :value="types.DATE" />
                              <el-option label="DateTime" :value="types.DATETIME" />
                            </el-select>
                          </el-form-item>
                          <el-form-item
                            v-if="currentItem.valueType === types.REFERENCE"
                            :label="$t('flyparam.reference_table')"
                            prop="table"
                          >
                            <div class="flex items-center">
                              <el-select
                                v-model="currentItem.table"
                                class="el-select__reference"
                                size="small"
                                value-key="id"
                                :loading="selectLoading"
                                @visible-change="selectTableVisibleChange"
                                @change="handleReferenceTableChange"
                              >
                                <el-option
                                  v-for="item in (currentTab &&
                                    currentTab.referenceTable) ||
                                  []"
                                  :key="item.id"
                                  :value="{
                                    id: item.id,
                                    tableType: item.tableType,
                                  }"
                                  :label="item.name"
                                >
                                  <svg-icon
                                    :icon-class="_treeIconClass(item)"
                                    class-name="mr-2"
                                  />
                                  <span>{{ item.name }}</span>
                                </el-option>
                              </el-select>
                              <div class="el-select-append">
                                <button
                                  type="button"
                                  class="el-button el-button--default"
                                  @click="() => handleReferenceTableClick()"
                                >
                                  <i class="el-icon-position" />
                                </button>
                              </div>
                            </div>
                          </el-form-item>
                          <el-form-item
                            v-if="
                              [types.TIME, types.DATE, types.DATETIME].includes(
                                currentItem.valueType
                              )
                            "
                            prop="formatter"
                            :label="$t('flyparam.date_formatter')"
                          >
                            <el-select
                              v-model="currentItem.formatter"
                              size="small"
                              @change="(val) => handleFormatterChange(val)"
                            >
                              <div v-if="currentItem.valueType === types.TIME">
                                <el-option label="HH:mm:ss" value="HH:mm:ss" />
                              </div>
                              <div v-else-if="currentItem.valueType === types.DATETIME">
                                <el-option
                                  label="MM/dd/yyyy HH:mm:ss"
                                  value="MM/dd/yyyy HH:mm:ss"
                                />
                                <el-option
                                  label="dd/MM/yyyy HH:mm:ss"
                                  value="dd/MM/yyyy HH:mm:ss"
                                />
                                <el-option
                                  label="yyyy/MM/dd HH:mm:ss"
                                  value="yyyy/MM/dd HH:mm:ss"
                                />
                                <el-option
                                  label="YYYY-MM-dd HH:mm:ss"
                                  value="YYYY-MM-dd HH:mm:ss"
                                />
                              </div>
                              <div v-else-if="currentItem.valueType === types.DATE">
                                <el-option label="MM/dd/yyyy" value="MM/dd/yyyy" />
                                <el-option label="dd/MM/yyyy" value="dd/MM/yyyy" />
                                <el-option label="yyyy/MM/dd" value="yyyy/MM/dd" />
                                <el-option label="YYYY-MM-dd" value="YYYY-MM-dd" />
                              </div>
                            </el-select>
                          </el-form-item>
                          <el-form-item
                            v-if="
                              !(
                                currentItem.valueType === types.REFERENCE &&
                                currentItem.table &&
                                currentItem.table.tableType === 1
                              )
                            "
                            prop="defaultValues"
                            :label="$t('flyparam.default_value')"
                            :rules="[{ validator: validateDefaultValues }]"
                          >
                            <div v-if="currentItem.valueType === types.BOOLEAN">
                              <el-radio-group
                                v-model="currentItem.defaultValues"
                                @change="handleBooleanChange"
                              >
                                <el-radio :label="true">{{
                                  $t("flyparam.true_option")
                                }}</el-radio>
                                <el-radio :label="false">{{
                                  $t("flyparam.false_option")
                                }}</el-radio>
                              </el-radio-group>
                            </div>
                            <div v-else-if="currentItem.valueType === types.REFERENCE">
                              <div
                                v-if="
                                  currentItem.table && currentItem.table.tableType === 2
                                "
                                class="flex items-center"
                              >
                                <el-select
                                  v-model="currentItem.defaultValues"
                                  class="el-select__reference"
                                  multiple
                                  :multiple-limit="currentItem.maxLength"
                                  collapse-tags
                                  size="small"
                                >
                                  <el-option
                                    v-for="item in currentItem.defaultValues"
                                    :key="item"
                                    :label="item"
                                    :value="item"
                                  />
                                </el-select>
                                <div class="el-select-append">
                                  <button
                                    type="button"
                                    class="el-button"
                                    @click="handleSelectReference(currentItem)"
                                  >
                                    {{ $t("flyparam.select") }}
                                  </button>
                                </div>
                              </div>
                              <el-select
                                v-else
                                v-model="currentItem.defaultValues"
                                v-loadmore="handleLoadMoreReference"
                                multiple
                                :disabled="refTableType !== 3"
                                @visible-change="
                                  (visible) => fetchReferenceData(visible, true)
                                "
                              >
                                <el-option
                                  v-for="item in referenceValues"
                                  :key="item"
                                  :label="item"
                                  :value="item"
                                />
                              </el-select>
                            </div>
                            <div v-else-if="currentItem.valueType === types.FILE">
                              <div v-ellipsis>
                                <span>{{ currentItem.defaultValues }}</span>
                              </div>
                              <el-button
                                size="small"
                                @click="handleSelectFile(activeId)"
                                >{{ $t("flyparam.select_file") }}</el-button
                              >
                            </div>
                            <el-input
                              v-else-if="currentItem.valueType === types.NUMBER"
                              v-model="currentItem.defaultValues"
                              size="small"
                              :placeholder="currentItem.defaultValues"
                              @input="(val) => handleNumberInputChange(val, currentItem)"
                            />
                            <el-input
                              v-else-if="currentItem.valueType === types.HEX"
                              v-model="currentItem.defaultValues"
                              size="small"
                              :placeholder="currentItem.defaultValues"
                              @input="(val) => handleHexInputChange(val, currentItem)"
                            />
                            <el-date-picker
                              v-else-if="currentItem.valueType === types.DATE"
                              v-model="currentItem.defaultValues"
                              type="date"
                              :format="currentItem.formatter"
                              :placeholder="$t('flyparam.choose_time_tip')"
                              size="small"
                              style="width: 100%"
                              @change="
                                (value) => (currentTab.form[currentItem.i] = value)
                              "
                            />

                            <el-date-picker
                              v-else-if="currentItem.valueType === types.DATETIME"
                              v-model="currentItem.defaultValues"
                              type="datetime"
                              :format="currentItem.formatter"
                              :placeholder="$t('flyparam.choose_time_tip')"
                              size="small"
                              style="width: 100%"
                              @change="
                                (value) => (currentTab.form[currentItem.i] = value)
                              "
                            />
                            <el-time-picker
                              v-else-if="currentItem.valueType === types.TIME"
                              v-model="currentItem.defaultValues"
                              :format="currentItem.formatter"
                              :value-format="currentItem.formatter"
                              :placeholder="$t('flyparam.choose_time_tip')"
                              size="small"
                              style="width: 100%"
                              @change="
                                (value) => (currentTab.form[currentItem.i] = value)
                              "
                            />
                            <el-input
                              v-else
                              v-model="currentItem.defaultValues"
                              :value="currentItem.defaultValues"
                              size="small"
                              :maxlength="currentItem.maxLength"
                              @input="(val) => handleStringInput(val, currentItem)"
                            />
                          </el-form-item>
                          <el-form-item
                            v-if="
                              currentItem.valueType === types.REFERENCE &&
                              currentItem.table &&
                              currentItem.table.tableType !== 1
                            "
                            :rules="[{ validator: validateMaxLength, maxLength: 100 }]"
                            :label="$t('flyparam.max_ref_number')"
                            prop="maxLength"
                          >
                            <el-input
                              v-model.number="currentItem.maxLength"
                              size="small"
                              type="number"
                              :max="100"
                            />
                          </el-form-item>
                          <el-form-item
                            v-else-if="currentItem.valueType === types.HEX"
                            :rules="[
                              {
                                validator: validateMaxLength,
                                maxLength: 2048,
                                trigger: 'change',
                              },
                            ]"
                            :label="$t('flyparam.max_length')"
                            prop="maxLength"
                          >
                            <el-input
                              v-model.number="currentItem.maxLength"
                              size="small"
                              type="number"
                              max="2048"
                            />
                          </el-form-item>
                          <template v-else-if="currentItem.valueType === types.NUMBER">
                            <el-form-item
                              :rules="[
                                {
                                  validator: validateMaxLength,
                                  maxLength: 999999999999999,
                                  trigger: 'change',
                                },
                              ]"
                              :label="$t('flyparam.max_value')"
                              prop="maxLength"
                            >
                              <el-input
                                v-model.number="currentItem.maxLength"
                                size="small"
                                type="number"
                                :max="999999999999999"
                              />
                            </el-form-item>
                            <el-form-item
                              :rules="[
                                {
                                  validator: validateMinLength,
                                  maxLength: 999999999999999,
                                  trigger: 'change',
                                },
                              ]"
                              :label="$t('flyparam.min_value')"
                              prop="maxLength"
                            >
                              <el-input
                                v-model.number="currentItem.minLength"
                                size="small"
                                type="number"
                                :max="999999999999999"
                              />
                            </el-form-item>
                          </template>
                          <el-form-item
                            v-else-if="
                              ![
                                types.BOOLEAN,
                                types.FILE,
                                types.TIME,
                                types.DATE,
                                types.DATETIME,
                              ].includes(currentItem.valueType) &&
                              !(
                                currentItem.valueType === types.REFERENCE &&
                                currentItem.table &&
                                currentItem.table.tableType === 1
                              )
                            "
                            :rules="[
                              {
                                validator: validateMaxLength,
                                maxLength: 255,
                                trigger: 'change',
                              },
                            ]"
                            :label="$t('flyparam.max_length')"
                            prop="maxLength"
                          >
                            <el-input
                              v-model.number="currentItem.maxLength"
                              size="small"
                              type="number"
                              max="255"
                            />
                          </el-form-item>
                          <el-form-item
                            v-if="
                              currentItem.valueType !== types.BOOLEAN &&
                              !(
                                currentItem.valueType === types.REFERENCE &&
                                currentItem.table &&
                                currentItem.table.tableType === 1
                              )
                            "
                            :label="$t('flyparam.param_require')"
                          >
                            <el-switch
                              v-model="currentItem.nullable"
                              :active-value="0"
                              :inactive-value="1"
                            />
                          </el-form-item>
                          <el-form-item :label="$t('common.description')">
                            <el-input
                              v-model="currentItem.description"
                              type="textarea"
                              maxlength="255"
                              show-word-limit
                              :autosize="{ minRows: 8 }"
                            />
                          </el-form-item>
                          <el-form v-if="hasPermissionControlPermission">
                            <el-form-item :label="$t('flyparam.parameter_authority')">
                              <div v-if="currentItem.new" class="flex flex-wrap">
                                <el-tooltip
                                  v-for="i in currentItem.permissionAssignedUsers
                                    ? currentItem.permissionAssignedUsers
                                    : []"
                                  :key="i.userId"
                                  :content="i.userName"
                                  placement="top"
                                >
                                  <img class="member-avatar" :src="_avatar(i.logoUrl)" />
                                </el-tooltip>
                              </div>
                              <div v-else class="flex flex-wrap">
                                <el-tooltip
                                  v-for="i in members.filter(
                                    (_) => _.paramPermissionControl === 1
                                  )"
                                  :key="i.userId"
                                  :content="i.username"
                                  placement="top"
                                >
                                  <img class="member-avatar" :src="_avatar(i.logoUrl)" />
                                </el-tooltip>
                              </div>
                              <el-button
                                type="primary"
                                size="small"
                                @click="() => (authorityDialogVisible = true)"
                              >
                                Edit Members
                              </el-button>
                            </el-form-item>
                          </el-form>
                          <el-form-item />
                        </div>
                      </el-form>
                    </div>
                  </el-col>
                </el-row>
                <param-list
                  v-else-if="page === 'ParamList'"
                  :ref="tab.id + '_paramList'"
                  disabled
                  :tab="currentTab"
                  :tab-id="currentTabId"
                  :reference-tables="currentTab && currentTab.referenceTable"
                  :table-data="currentLayout.filter((v) => v.i !== 'new' && v.type === 1)"
                  :table-id="currentTabId"
                  :template-id="templateId"
                  :mode="mode"
                  @save="
                    (data) => {
                      currentTab.data = data;
                      currentLayout = currentTab.data;
                      activeId = '';
                    }
                  "
                />
                <entry-list
                  v-else-if="page === 'EntryList' && tab.id === currentTabId"
                  :key="tab.id"
                  ref="entryList"
                  :table-id="currentTabId"
                  :app-name="innerAppName"
                  :sn="currentTab.name"
                  :table-type="tableType"
                  :reference-table="currentTab && currentTab.referenceTable"
                  :template-id="templateId"
                />
              </keep-alive>
            </el-card>
          </el-tab-pane>
        </el-tabs>
      </el-col>
    </el-row>
    <common-component-dialog
      v-model="newComponentVisible"
      :component.sync="comoponentDialogData"
      :operation-type="newComponentDialogType"
      @confirm="fetchCommonComponents"
    />
    <upload-file
      v-model="uploadFileVisible"
      :current-item="currentItem"
      @confirm="(filePath) => handleFileUploadConfirm(filePath)"
    />
    <new-table
      v-model="newTableVisible"
      :template-id="templateId"
      @confirm="(tableName) => handleNewTable(tableName)"
    />
    <select-reference
      :current-item="currentItem"
      :template-id="templateId"
      v-model="selectReferenceVisible"
      :table-id="currentItem && currentItem.table && currentItem.table.id"
    />
    <preview-drawer
      ref="previewDrawer"
      size="60%"
      :xml="xml"
      v-model="jsonDrawerVisible"
      :content="jsonStr"
      :file-type="previewDrawerFileType"
      :loading="previewDrawerLoading"
      @refresh="(fileType) => previewTemplate(fileType)"
    />
    <rename-dialog
      v-model="renameDialogVisible"
      :table-id="renameId"
      :tabs="tabs"
      :table-name="renameName"
      @save="fetchTemplateTableList"
    />
    <authority-dialog
      :permission-assigned-users="currentItem && currentItem.permissionAssignedUsers"
      v-model="authorityDialogVisible"
      :table-id="currentTab && currentTab.id"
      :template-id="templateId"
      :param="currentItem"
      :param-id="currentItem && currentItem.paramId"
      @update="
        (selectedMembers) => {
          fetchParameterAuthority();
          if (currentItem.new) {
            currentItem.permissionAssignedUsers = selectedMembers;
          }
        }
      "
    />
    <import-export-dialog
      :mode="mode"
      :authorized="$store.getters.btnPermissions.includes(PERMISSIONS.DESIGN)"
      :template-id="templateId"
      :param-template-id="paramTemplateId"
      v-model="importExportVisible"
      :tree-data="tables"
      :table-type="tableType"
      :table-id="tableId"
    />
    <component-import
      v-model="componentImportVisible"
      :tree-data="commonComponents"
      :component-ids="componentIds"
      @confirm="fetchCommonComponents"
    />
    <layout-preview
      :table-id="refTableId"
      v-model="layoutPreviewVisible"
      :template-id="templateId"
    />
    <undo-dialog v-model="undoDialogVisible" @confirm="(type) => undo(type)" />
    <redesign-dialog
      :template-id="paramTemplateId"
      v-model="redesignDialogVisible"
      @confirm="handleRedesignConfirm"
    />
    <new-reference-dialog
      :template-id="templateId"
      :table-id="currentTab && currentTab.id"
      v-model="newReferenceDialogVisible"
      :component="selectedReferenceComponent"
      @update-table="
        (tableId) => {
          fetchTemplateTableList();
          fetchReferenceTable(tableId);
        }
      "
      @confirm="(table, tableId) => handleNewReference(table, tableId)"
    />

    <el-dialog
      :title="$t('flyparam.page_preview')"
      v-model="configPreviewDialogVisible"
      width="80%"
    >
      <configuration-preview
        :key="configurationPreviewKey"
        :info="this.tables[0]"
        :layout="this.currentTab && this.currentTab.data"
      />
    </el-dialog>
  </div>
</template>

<script>
import VueGridLayout from "vue-grid-layout";
import Parameter from "./components/parameter";
import ParamList from "./components/param-list";
import EntryList from "./components/entry-list";
import UploadFile from "./components/upload-file";
import SelectReference from "./components/select-reference";
import LayoutPreview from "./components/layout-preview.vue";
import PreviewDrawer from "./components/preview-drawer";
import RenameDialog from "./components/rename-dialog";
import RedesignDialog from "./components/redesign-dialog";
import base64 from "@/utils/base64";
import { types } from "./constant";
import { uuidv4, deepClone, isObjectBoolean } from "@/utils";
import { rules, layoutRules } from "./rules";
import { validateMaxLength, validateMinLength } from "./validate";
import { treeIconClass, avatar, formatDateParameter, parseDate } from "./helper";
import PopupMenu from "./components/popup-menu.vue";
import {
  getParameterTemplateInfo,
  getTemplateTableLayoutList,
  getTemplateTableList,
  saveTemplateLayout,
  getBasicComponent,
  getReferenceTable,
  getCommonComponentList,
  deleteCommonComponent,
  getParameterTemplatePreview,
  deleteTable,
  getReferenceEntryRecord,
  getParameterTemplateInfoByPackageName,
  createBlankTemplate,
  runBatch,
  getParameterAuthority,
} from "@/api/parameter";
import NewTable from "./components/new-table";
import ConfigurationPreview from "./components/configuration-preview.vue";
import { mapGetters } from "vuex";
import { hasPermission } from "@/utils/auth";
import { PERMISSIONS } from "@/components/common/const";
import Vue from "vue";

var formatXml = require("xml-formatter");
const watchers = [];

const generateLayoutData = function (item, referenceTables) {
  let table;
  if (item.valueType === 5) {
    const refTable = referenceTables.find((table) => table.id === item.refTableSchemaId);
    if (refTable) {
      table = {
        id: item.refTableSchemaId,
        tableType: refTable.tableType,
        name: refTable.name,
      };
    }
  }
  return {
    ...item,
    i: item.paramLayoutId,
    title: item.title,
    valueType: item.valueType,
    type: item.type,
    w: item.w,
    y: item.y,
    x: item.x,
    h: item.h,
    label: item.label,
    table: table,
    editable: false,
    defaultValues:
      item.valueType === this.types.REFERENCE
        ? item.defaultValues
        : item.defaultValues && item.defaultValues.length === 1
        ? item.defaultValues[0]
        : "",
  };
};

const mouseXY = { x: null, y: null };
const DragPos = { x: null, y: null, w: 1, h: 2, i: null };
const UNDO_ALL = 1;
const UNDO_CURRENT = 2;

const populateTabData = (arr, tabs, triggerPublish) => {
  tabs.forEach((tab) => {
    arr.push({
      triggerPublish,
      paramSchemaDesignChange: tab.paramSchemaDesignChange,
      tableId: tab.id,
      parameters: tab.data
        .filter((item) => item.i !== "new")
        .map((item) => {
          const defaultValues = [];

          if (isObjectBoolean(item.defaultValues)) {
            defaultValues.push(item.defaultValues);
          } else if (item.defaultValues || item.defaultValues === 0) {
            if (Array.isArray(item.defaultValues)) {
              return {
                ...item,
                defaultValues: item.defaultValues,
                refTableSchemaId: item.table ? item.table.id : "",
              };
            } else {
              if (item.valueType === types.DATE || item.valueType === types.DATETIME) {
                const dateValue = formatDateParameter(item, "defaultValues");
                defaultValues.push(dateValue);
              } else {
                defaultValues.push(item.defaultValues);
              }
            }
          }

          let permissionAssignedUsers;

          if (item.new && item.selectAllMembers) {
            permissionAssignedUsers = new Array();
          } else {
            permissionAssignedUsers =
              item.permissionAssignedUsers &&
              item.permissionAssignedUsers.map((_) => ({
                userId: _.userId,
                userName: _.userName,
              }));
          }

          return {
            ...item,
            defaultValues,
            refTableSchemaId: item.table ? item.table.id : "",
            permissionAssignedUsers,
          };
        }),
    });
  });
};

const mapWatchData = (data) => ({
  defaultValues: data.defaultValues,
  title: data.title,
  label: data.label,
  valueType: data.valueType,
  maxLength: data.maxLength,
  minLength: data.minLength,
  key: data.key,
  formatter: data.formatter,
  table: data.table,
  nullable: data.nullable,
  paramLayoutId: data.paramLayoutId,
  description: data.description,
  permissionAssignedUsers: data.permissionAssignedUsers,
  horizontalAlign: data.horizontalAlign,
});

function validateDefaultValues(rule, value, callback) {
  if (
    this.currentItem.valueType === types.STRING ||
    this.currentItem.valueType === types.HEX ||
    this.currentItem.valueType === types.REFERENCE
  ) {
    if (value.length > this.currentItem.maxLength) {
      return callback(new Error(this.$t("flyparam.length_exceeds")));
    }
  } else if (this.currentItem.valueType === types.NUMBER) {
    if (value.length > 0) {
      if (!/^[+-]?((\d+(\.\d*)?)|(\.\d+))$/.test(value)) {
        return callback(
          new Error(
            this.$t("flyparam.invalid_input") +
              "(" +
              this.$t("flyparam.type_number") +
              ")"
          )
        );
      }
      // 第一位不能为0
      if (value.length > 1) {
        if (/^[0]/.test(value)) {
          return callback(
            new Error(
              this.$t("flyparam.invalid_input") +
                "(" +
                this.$t("flyparam.type_number") +
                ")"
            )
          );
        }
      }
    }

    if (Number(value) > this.currentItem.maxLength) {
      return callback(new Error(this.$t("flyparam.max_number_exceeds")));
    }
    if (Number(value) < this.currentItem.minLength) {
      return callback(new Error(this.$t("flyparam.number_too_small")));
    }
    callback();
  } else {
    return callback();
  }
  return callback();
}

export default {
  components: {
    GridLayout: VueGridLayout.GridLayout,
    GridItem: VueGridLayout.GridItem,
    Parameter,
    ParamList,
    EntryList,
    NewTable,
    UploadFile,
    SelectReference,
    CommonComponentDialog: () => import("./components/common-components-dialog.vue"),
    PreviewDrawer,
    RenameDialog,
    ImportExportDialog: () => import("./components/import-export-dialog.vue"),
    ComponentImport: () => import("./components/component-import-export.vue"),
    LayoutPreview,
    UndoDialog: () => import("./components/undo-dialog.vue"),
    RedesignDialog,
    ConfigurationPreview,
    NewReferenceDialog: () => import("./components/new-refence-dialog.vue"),
    AuthorityDialog: () => import("./components/authority-dialog"),
  },

  props: {
    paramTemplateId: {
      type: String,
    },
    appPackageName: {
      type: String,
    },
    appName: {
      type: String,
    },
    showBack: {
      type: Boolean,
      default: true,
    },
    source: {
      type: String,
      default: "",
    },
    disableSettings: {
      type: Boolean,
      default: false,
    },
  },

  provide() {
    return {
      eventBus: null,
    };
  },

  data() {
    var that = this;
    return {
      PERMISSIONS: { ...Object.freeze(PERMISSIONS.FLY_PARAM) },
      paramPermissionControl: 0,
      validateDefaultValues: validateDefaultValues.bind(this),
      innerAppName: "",
      mode: 1,
      activeId: "0",
      currentTabId: "",
      tabs: [],
      xml: "",
      members: [], // 权限控制成员列表
      draging: false,
      page: "Design",
      newTableVisible: false,
      selectedFileParameter: "",
      uploadFileVisible: false,
      selectReferenceVisible: false,
      newReferenceDialogVisible: false,
      jsonDrawerVisible: false,
      componentImportVisible: false,
      layoutLoading: false,
      importExportVisible: false, // 导入导出窗口
      layoutPreviewVisible: false,
      renameDialogVisible: false,
      undoDialogVisible: false,
      redesignDialogVisible: false,
      configPreviewDialogVisible: false,

      // 引用类型下拉多选框相关数据
      referenceValues: [],
      referencePage: 1,
      totalReferences: 1,

      // 通用组件相关
      newComponentVisible: false,
      newComponentDialogType: "new",
      comoponentDialogData: {}, // 需要更新的组件

      authorityDialogVisible: false, // 权限控制窗口
      currentLayout: [],
      rules: rules.call(that),
      layoutRules: layoutRules.call(that),
      layout: [],
      layoutMap: [],
      tables: [],
      selectedReferenceComponent: {},
      referenceTable: [],
      activeComponentTab: "basic",
      commonComponents: [],
      componentIds: [],
      basicComponents: [],
      defaultProps: {
        children: "children",
        label: "name",
      },
      types,
      selectLoading: false,
      paramPropertiesLoading: false,
      jsonStr: "", // 预览的json数据
      previewDrawerFileType: 1,
      previewDrawerLoading: false,
      pushingData: false,
      renameId: "",
      templateId: this.$route.params.id || this.paramTemplateId || "",
      renameName: "",
      randomKey: "",
      mainContentKey: 0,
      configurationPreviewKey: "",
      validateMaxLength: validateMaxLength.bind(this),
      validateMinLength: validateMinLength.bind(this),
      contentHeight: "100%",
    };
  },
  computed: {
    ...mapGetters(["avatars"]),
    hasDesignPermission() {
      // 是否拥有设计权限
      return hasPermission(this.PERMISSIONS.DESIGN);
    },
    hasPermissionControlPermission() {
      // 是否拥有参数分配权限的权限
      return (
        hasPermission(this.PERMISSIONS.PERMISSION_CONTROL) &&
        this.paramPermissionControl === 1 &&
        this.tableType === 1
      );
    },
    // 被选中的当前参数
    currentItem() {
      return this.currentLayout.find((item) => item.i === this.activeId);
    },
    // 模板是否有变动
    changed() {
      const tabs = this.tabs.filter(
        (tab) =>
          tab.tableType !== 3 && (tab.paramSchemaDesignChange || tab.positionChanged)
      );
      return tabs.length > 0;
    },
    // table的类型，主要用于图标的处理
    tableType() {
      if (this.isProfile) {
        return 1;
      } else if (this.currentTab) {
        return this.currentTab.tableType;
      } else {
        return 1;
      }
    },
    tableId() {
      return this.tables.length > 0 ? this.tables[0].id : "";
    },
    currentTab() {
      return this.tabs.find((tab) => tab.id === this.currentTabId);
    },
    // 新增按钮（简单模式下）的x坐标
    xPosition() {
      return this.currentLayout.length === 0 ? 0 : (this.currentLayout.length - 1) % 2;
    },
    // 新增按钮（简单模式下）的y坐标
    yPosition() {
      return this.currentLayout.length === 0 ? 0 : this.currentLayout.length + 2;
    },
    // 每行有多少参数，简单模式下为2个
    colNumber() {
      if (this.mode === 1) {
        return 3;
      } else {
        return 2;
      }
    },
    // 当前选中的Table
    currentTable() {
      return this.tables.find((table) => table.id === this.currentTabId) || "";
    },
    isProfile() {
      return this.currentTable && this.currentTable.name === "Profile";
    },
    refTableType() {
      if (this.currentItem && this.currentItem.table) {
        return this.currentItem.table.tableType;
      }
      return "";
    },
    refTableId() {
      if (this.currentItem && this.currentItem.table) {
        return this.currentItem.table.id;
      }
      return "";
    },
    dataString() {
      const arr = this.currentLayout.filter((_) => _.i !== "new").map(mapWatchData);
      return arr.length > 0 ? JSON.stringify(arr) : "[]";
    },
    positionString() {
      const arr = this.currentLayout
        .filter((_) => _.i !== "new")
        .map((_) => ({
          x: _.x,
          y: _.y,
          w: _.w,
          h: _.h,
        }));
      return arr.length > 0 ? JSON.stringify(arr) : "[]";
    },
    from() {
      return this.$route.query && this.$route.query.from;
    },
  },
  beforeRouteLeave(to, from, next) {
    if (!this.from) {
      this.$store.commit("app/REMOVE_KEEP_ALIVE_LIST", "FlyParameter");
    }
    next();
  },
  watch: {
    activeId() {
      if (this.$refs.propertiesForm && this.$refs.propertiesForm[0]) {
        this.$refs.propertiesForm[0].clearValidate();
      }
      if (
        this.activeId &&
        this.hasPermissionControlPermission &&
        this.currentItem &&
        this.currentItem.type !== 2
      ) {
        this.fetchParameterAuthority();
      }
      if (this.$refs.gridLayout) {
        this.$refs.gridLayout.layoutUpdate();
      }
    },
    page(value) {
      if (value === "ParamList") {
        console.log(this.$refs);
        this.$nextTick(() => {
          this.$refs[this.currentTabId + "_paramList"][0].fetchReferenceTable();
        });
      }
    },
  },
  beforeDestroy() {
    watchers.forEach((unwatch) => unwatch());
  },
  mounted() {
    const container = document.createElement("div");
    container.id = "popup-menu";
    document.body.appendChild(container);
    if (this.appPackageName) {
      this.fetchInfoByPackageName(this.appPackageName);
    } else {
      this.initTemplateConfigPage();
    }
    document.addEventListener(
      "dragover",
      function (e) {
        mouseXY.x = e.clientX;
        mouseXY.y = e.clientY;
      },
      false
    );
  },
  methods: {
    initTemplateConfigPage() {
      this.fetchParameterTemplateInfo();
      if (this.mode === 1) {
        // 获取组件配置
        this.fetchBasicComponent();
        this.fetchCommonComponents();
      } else if (this.mode === 2) {
        // 简单模式下直接获取布局配置
        this.fetchTemplateTableLayoutList();
      }
    },
    // 从头像库中找到用户对应的头像
    _avatar(logoUrl) {
      return avatar(this.avatars, logoUrl);
    },
    async fetchInfoByPackageName(appPackageName) {
      try {
        const info = await getParameterTemplateInfoByPackageName(appPackageName);
        this.templateId = info.id;
        if (info) {
          this.initTemplateConfigPage();
        } else {
          const res = await createBlankTemplate({
            mode: this.mode,
            appName: this.appName,
            appPackageName: appPackageName,
            fileType: 1,
          });
          this.templateId = res.id;
          this.initTemplateConfigPage();
        }
      } catch (err) {
        if (err.message) {
          this.$message.error(err.message);
        }
      }
    },
    back(cb) {
      if (
        this.tabs.filter((tab) => tab.paramSchemaDesignChange || tab.positionChanged)
          .length > 0
      ) {
        this.$commonConfirm({
          title: this.$t("page.tip"),
          content: this.$t("flyparam.unsave"),
          btnSureTitle: this.$t("flyparam.unsave_close"),
          type: "danger",
          showIcon: false,
          onConfirm: (instance, done) => {
            if (cb) {
              cb();
            } else {
              this.$router.replace(`/fly-parameter/index`);
            }
            done();
          },
        });
      } else {
        if (cb) {
          cb();
        } else {
          this.$router.replace(`/fly-parameter/index`);
        }
      }
    },
    _treeIconClass(data) {
      return treeIconClass(data);
    },

    expandTreeNode(id) {
      const node = this.$refs.tableTree.getNode(id);
      node.expanded = !node.expanded;
    },
    /**
     * 获取应用的参数模板信息
     */
    fetchParameterTemplateInfo() {
      this.tables = [];
      this.tabs = [];
      getParameterTemplateInfo(this.templateId).then((data) => {
        const id = data.entryId;
        this.tables.push({ ...data, id: data.entryId });
        this.currentTabId = id;
        this.newTab({ ...data, id: data.entryId, name: "Profile" });
        this.innerAppName = data.appName;
        this.$store.commit("parameter/SET_TEMPLATE_INFO", data);
        this.$store.commit("parameter/SET_APP_NAME", data.appName);
        this.mode = data.mode;
        this.paramPermissionControl = data.paramPermissionControl; // 权限修改的控制开关
        this.fetchTemplateTableList();
      });
    },
    /**
     *  引用类型的参数下拉列表翻页
     */
    handleLoadMoreReference() {
      this.referencePage++;
      this.fetchReferenceData(true);
    },
    /**
     * 引用类型参数下拉列表打开时查询该参数能引用的表
     * @param {Boolean} visible - el-select的visible-change的参数，true表示下拉列表显示
     * @param {Boolean} triggerByDropDown - 是否是点击下拉列表而非翻页的查询
     */
    fetchReferenceData(visible, triggerByDropDown) {
      if (visible) {
        if (triggerByDropDown) {
          this.referenceValues = [];
          this.totalReferences = 1;
          this.referencePage = 1;
        }
        if (this.totalReferences <= this.referenceValues.length) {
          return;
        }
        getReferenceEntryRecord(this.templateId, this.currentItem.table.id, {
          pageNo: this.referencePage,
          pageSize: 10,
        })
          .then((data) => {
            const value = data.items.map((item) => {
              return item.$label[0];
            });
            this.totalReferences = data.total;
            this.referenceValues.push(...value);
          })
          .finally(() => {
            this.loading = false;
          });
      }
    },

    fetchTemplateTableList() {
      return new Promise((resolve, reject) => {
        getTemplateTableList(this.templateId)
          .then((data) => {
            this.$store.commit("parameter/SET_PROFILE_TABLES", data);
            this.$set(
              this.tables[0],
              "children",
              data.filter((item) => item.name !== "Profile")
            );
            const profile = data.find((item) => item.id === this.tables[0].entryId);
            this.$set(this.tables, 0, {
              ...this.tables[0],
              id: profile.id,
              name: profile.name,
              tableType: profile.tableType,
            });
            resolve(data);
          })
          .catch((err) => {
            this.$message.error(err);
            reject(err);
          });
      });
    },

    fetchBasicComponent() {
      getBasicComponent().then((data) => {
        this.basicComponents = [
          ...data,
          {
            category: "divider",
            title: "divider",
            label: "divider",
            w: 3,
            h: 1,
            type: 2,
          },
        ];
      });
    },

    async fetchParameterAuthority() {
      if (this.currentItem.new) {
        return;
      }
      this.paramPropertiesLoading = true;
      try {
        const res = await getParameterAuthority(
          this.templateId,
          this.currentTab.id,
          this.currentItem.paramId
        );
        this.members = res;
        this.paramPropertiesLoading = false;
      } catch (err) {
        this.$message.error(err);
        this.members = [];
        this.paramPropertiesLoading = false;
      }
    },

    fetchCommonComponents() {
      getCommonComponentList().then((data) => {
        this.componentIds = data.map((o) => o.componentId);
        const arr = Array.from(new Set(data.map((item) => item.category)));
        const commonComponents = arr.map((item) => ({
          label: item,
          children: [],
        }));
        data.forEach((item) => {
          const parent = commonComponents.find((v) => v.label === item.category);
          parent.children.push({ ...item, isCustom: true });
        });
        this.commonComponents = commonComponents;
      });
    },

    selectTableVisibleChange(val) {
      if (val) {
        this.fetchReferenceTable(this.currentTabId);
      }
    },

    fetchReferenceTable(tableId) {
      return new Promise((resolve, reject) => {
        this.selectLoading = true;
        getReferenceTable(this.templateId, tableId)
          .then((data) => {
            if (this.currentTab) {
              this.currentTab.referenceTable = data;
            } else {
              this.referenceTable = data;
            }
            resolve(data);
          })
          .catch((err) => {
            reject(err);
          })
          .finally(() => {
            this.selectLoading = false;
          });
      });
    },

    async fetchTemplateTableLayoutList(id) {
      let referenceTable;
      this.layoutLoading = true;
      if (this.mode === 1) {
        referenceTable = await this.fetchReferenceTable(id);
      }

      const res = await getTemplateTableLayoutList(this.templateId, id);
      this.layoutLoading = false;
      // 防止连续点击的时候因为异步的问题导致添加了重复的选项卡
      this.pushingData = false;

      // 组成符合格式的数组
      const layoutData = res.map((item) => {
        return generateLayoutData.call(this, item, referenceTable);
      });

      return { layoutData, referenceTable };
    },

    initTabData(data, layoutData, referenceTable, openNewTab) {
      const form = {};
      layoutData.forEach((data) => {
        if ([types.DATE, types.DATETIME].includes(data.valueType) && data.defaultValues) {
          data.defaultValues = parseDate(data.defaultValues, data.formatter);
        }
        form[data.i] = isObjectBoolean(data.defaultValues)
          ? data.defaultValues
          : data.defaultValues || "";
      });

      this.tabs.push({
        ...data,
        data: layoutData,
        randomKey: Math.random(),
        referenceTable,
        positionChanged: false,
        reservePosition: JSON.stringify(
          layoutData.map((_) => ({
            x: _.x,
            y: _.y,
            w: _.w,
            h: _.h,
          }))
        ),
        reserveJSON: JSON.stringify(layoutData.map(mapWatchData)),
        form,
        paramSchemaDesignChange: this.$route.query.unsaved,
        // 把初始数据保存到reserveData，进行撤销操作的时候直接拿来替换data
        reserveData: deepClone(layoutData),
        reserveForm: deepClone(form),
        tableType: data.tableType,
        layoutMap: this.generateArr(layoutData),
      });

      this.resizeContentHeight();

      // 初始化数据后默认高亮第一个参数
      if (openNewTab) {
        this.currentLayout = this.tabs[this.tabs.length - 1].data;
        this.activeId = this.currentLayout.length > 0 ? this.currentLayout[0].i : "";
      }
      // 监听参数的变化，发生变化就给tab打上*标记
      if (this.tableType !== 3) {
        watchers.push(
          this.$watch(
            (vm) => vm.dataString,
            function () {
              if (this.currentTab.reserveJSON !== this.dataString) {
                this.$set(this.currentTab, "paramSchemaDesignChange", true);
              } else {
                this.$set(this.currentTab, "paramSchemaDesignChange", false);
              }
            }
          )
        );

        watchers.push(
          this.$watch(
            (vm) => vm.positionString,
            function () {
              if (this.currentTab.reservePosition !== this.positionString) {
                this.$set(this.currentTab, "positionChanged", true);
              } else {
                this.$set(this.currentTab, "positionChanged", false);
              }
            }
          )
        );
      }

      // 简单模式下多一个新增按钮
      if (this.mode === 2) {
        this.currentLayout.push({
          x: this.currentLayout.length % 2,
          y: this.yPosition,
          i: "new",
          w: 1,
          h: 2,
        });
      }
    },

    handleTabClick(tab) {
      const _tab = this.tabs.find((v) => v.id === tab.name);
      this.currentLayout = _tab.data;
      _tab.randomKey = Math.random();
      this.resizeContentHeight();
      if (_tab.tableType === 3) {
        this.page = "EntryList";
      } else {
        this.page = "Design";
      }
    },

    handleTabRemove(id) {
      const removeTab = () => {
        this.tabs = this.tabs.filter((tab) => tab.id !== id);
        this.newTab(this.tabs[0]);
      };

      const tab = this.tabs.find((tab) => tab.id === id);
      if (tab.paramSchemaDesignChange) {
        this.$commonConfirm({
          title: this.$t("flyparam.close_tab"),
          content: `Your changes are unsaved, are you sure to close this tab?`,
          btnSureTitle: this.$t("common.confirm"),
          onConfirm: (instance, done) => {
            removeTab();
            done();
          },
        });
      } else {
        removeTab();
      }
    },

    resizeContentHeight() {
      // TODO: 高度问题导致的滚动条会遮住一部分主界面，这样做能解决，但还不是最好的方案
      this.$nextTick(() => {
        this.contentHeight = "100%";
        this.contentHeight = "calc(100vh - 180px)";
      });
    },

    handleDateFormatChange(val) {
      const date = parseDate(val);
      this.currentItem.defaultValues = val;
    },

    handleBooleanChange(val) {
      this.currentTab.form[this.currentItem.i] = val;
    },

    removeItem(id) {
      const index = this.currentLayout.map((item) => item.i).indexOf(id);
      this.currentLayout.splice(index, 1);
      this.currentTab.layoutMap = this.generateArr(this.currentTab.data);
    },
    generateName(name, prop = "label", table) {
      const reg = new RegExp(`${name}([0-9][0-9]*)`);
      let max = -1;
      let layout = [];
      if (table) {
        layout = table.data;
      } else {
        layout = this.currentLayout;
      }
      layout.forEach((item) => {
        if (item[prop] === name) {
          max = 0;
        }
        const matches = reg.exec(item[prop]);
        if (matches) {
          const index = Number(matches[1]);
          max = max >= index ? max : index;
        }
      });
      if (max >= 0) {
        return `${name}${Number(max + 1)}`;
      }
      return name;
    },
    handleNewReference(table, tableId) {
      this.fetchReferenceTable(tableId).then(() => {
        this.addItem(this.selectedReferenceComponent, true);
        this.newTab(table, false);
      });
    },

    tabLabel(tab) {
      const label = (tab.tableType !== 3 && this.hasChanged(tab)
        ? "* " + tab.name
        : tab.name
      ).slice(0, 10);
      return tab.name.length > 10 ? label + "..." : label;
    },

    /**
     * @desc 点击左侧参数列表"+"添加新的参数
     * @param {Object} data - 参数数据
     * @param {boolean} fromDialog - 是否是通过selectReferenceDialog添加的参数
     * @param {boolean} copy - 是否是通过复制添加的参数
     * @param {string} tableId - 向指定的table添加参数
     */
    addItem(data, fromDialog, copy, tableId) {
      if (!this.hasDesignPermission) {
        this.$message.error(`The user is unauthorized.`);
        return;
      }
      // 参数卡片的宽高，如果是divider宽高分别是3 和 1
      const w = data.type === 2 ? 3 : 1;
      const h = data.type === 2 ? 1 : 2;
      // 布局处于加载状态时，不可添加
      if (this.layoutLoading) {
        return;
      }
      // 点击的是一个目录，不可添加

      if (data.children) {
        return;
      }

      // 如果是引用类型，就打开选择引用表的弹窗
      if (data.valueType === 5 && !copy) {
        if (!fromDialog) {
          this.selectedReferenceComponent = data;
          return (this.newReferenceDialogVisible = true);
        }
      }

      let newItem;
      let table = null;
      if (this.mode === 1) {
        let label = "";

        // 赋予随机的唯一id
        const i = uuidv4();
        // 参数标签，每次新增的标签都不能与已存在的相同

        if (tableId) {
          table = this.tabs.find((tab) => tab.id === tableId);
        } else {
          table = this.currentTab;
        }

        if (copy) {
          label = this.generateName(data.label + "_copy", "label", table);
        } else {
          label = this.generateName(data.label, "label");
        }
        // isCustom = true 表示是自定义组件，用自己的key，否则就是和label相同
        const key = data.isCustom
          ? this.generateName(data.key, "key")
          : label.toLowerCase();
        let defaultValues = "";
        if (data.valueType === types.NUMBER) {
          defaultValues = 0;
        } else if (data.valueType === types.BOOLEAN) {
          defaultValues = false;
        }
        newItem = {
          i: copy ? data.i : i,
          valueType: data.valueType,
          type: data.type || 1,
          value: data.value,
          new: true,
          ...data,
          defaultValues,
          x: 0,
          y: table.layoutMap.length,
          w: copy ? data.w : w,
          h: copy ? data.h : h,
          key,
          label,
        };

        // 分割线组件多一个alignment属性
        if (data.type === 2) {
          newItem["horizontalAlign"] = "c";
        }
        // 主要是为了新参数插入的位置
        if (table.layoutMap.length) {
          for (let r = 0, rLen = table.layoutMap.length; r < rLen; r++) {
            for (let c = 0; c <= this.colNumber - w; c++) {
              const res = this.regionalTest(
                c,
                r,
                w,
                rLen > r + h ? h : rLen - r,
                table.layoutMap
              );

              if (res.result) {
                // 更新添加数据内容
                newItem = {
                  ...newItem,
                  x: res.x,
                  y: res.y,
                  w: w,
                  h: h,
                };

                c = this.colNumber + 1;
                r = rLen + 1;
              } else {
                c = res.offsetX;
              }
            }
          }
        }
        // 更新二维数组map，主要是为了新参数插入的位置
        for (let itemR = 0; itemR < h; itemR++) {
          for (let itemC = 0; itemC < w; itemC++) {
            if (!table.layoutMap[newItem.y + itemR]) {
              table.layoutMap[newItem.y + itemR] = new Array(this.colNumber);
              for (let i = 0; i < this.colNumber; i++) {
                table.layoutMap[newItem.y + itemR][i] = 0;
              }
            }
            // 标记点
            table.layoutMap[newItem.y + itemR][newItem.x + itemC] = 1;
          }
        }
      } else if (this.mode === 2) {
        // 简单模式下
        let key = "";
        if (copy) {
          key = this.generateName(data.label + "_copy");
        } else {
          key = this.generateName("string");
        }
        const newButton = this.currentLayout.find((layout) => layout.i === "new");
        newItem = {
          x: newButton.x,
          y: newButton.y,
          w: 1,
          h: 2,
          new: true,
          i: uuidv4(),
          nullable: 1,
          valueType: this.types.STRING,
          label: key,
          key: key,
          type: 1,
          defaultValues: "",
          maxLength: 255,
        };
      }
      if (tableId && this.mode === 1) {
        this.$set(table.form, newItem.i, newItem.defaultValues);
        table.data.push(newItem);
      } else {
        this.currentLayout.push(newItem);
        this.$set(this.currentTab.form, newItem.i, newItem.defaultValues);
        // this.$set(this.currentTab.form, newItem.i, '')
        this.activeId = newItem.i;
      }

      // if (this.mode === 2) {
      //   const item = this.currentLayout.find((item) => item.i === 'new')
      //   this.$set(item, 'x', this.xPosition)
      //   this.$set(item, 'y', this.yPosition * 2)
      // }
    },

    layoutReadyEvent() {
      // this.layoutMap = this.generateArr(this.currentLayout)
    },

    layoutUpdatedEvent() {
      if (this.mode === 2) {
        // 新增按钮
        const newButton = this.currentLayout.find((item) => item.i === "new");

        const filteredList = this.currentLayout.filter((item) => item.i !== "new");
        const maxY = Math.max(...filteredList.map((o) => o.y));
        const arrayWithMaxY = filteredList.filter((item) => item.y === maxY);
        let lastItem = null;
        if (arrayWithMaxY.length > 1) {
          lastItem = arrayWithMaxY.reduce(function (prev, current) {
            return prev.x > current.x ? prev : current;
          });
        } else {
          lastItem = arrayWithMaxY[0];
        }

        let x = 0;
        let y = 0;
        if (lastItem) {
          x = lastItem.x === 0 ? 1 : 0;
          y = lastItem.x === 0 ? lastItem.y : lastItem.y + 2;
        }

        Object.assign(newButton, { x, y });

        // console.log(arrayWithMaxY)
        // if (arrayWithMaxY.length > 1) {
        //   lastItem = arrayWithMaxY.reduce(function(prev, current) {
        //     return (prev.x > current.x) ? prev : current
        //   })
        // } else {
        //   lastItem = arrayWithMaxY[0]
        // }
      } else {
        // this.layoutMap = this.generateArr(this.currentLayout)
      }
    },

    // 检测是否有空位置
    regionalTest(x, y, w, h, layoutMap) {
      // 定义返回 x,y 偏移 及 是否有空位置
      let offsetX = 0;
      let offsetY = 0;
      let res = true;
      for (let r = 0; r < w; r++) {
        for (let c = 0; c <= h; c++) {
          const point = layoutMap[y + r] ? layoutMap[y + r][x + c] : 0;
          // 如该点被占据 记录偏移值
          if (point === 1) {
            res = false;
            offsetX = offsetX > x + c ? offsetX : x + c;
            offsetY = offsetY > y + r ? offsetY : y + r;
          }
        }
      }

      return {
        result: res,
        offsetX: offsetX,
        x: x,
        y: y,
      };
    },
    // 生成二维数组数据
    generateArr(data) {
      const map = [];
      if (Array.isArray(data)) {
        for (let i = 0; i < data.length; i++) {
          const one = data[i];
          // 循环行
          for (let r = one.y; r < one.y + one.h; r++) {
            // 循环列
            for (let c = one.x; c < one.x + one.w; c++) {
              // 检修当前行是否存在
              if (!map[r]) {
                map[r] = new Array(this.colNumber);

                for (let i = 0; i < this.colNumber; i++) {
                  map[r][i] = 0;
                }
              }
              // 占据为1
              map[r][c] = 1;
            }
          }
        }
      }
      return map;
    },

    handleFormatterChange(val, item) {
      this.$forceUpdate();
    },

    // 右侧valueType改变时的回调
    handleValueTypeChange(val) {
      if (
        [types.BOOLEAN, types.FILE, types.TIME, types.DATE, types.DATETIME].includes(val)
      ) {
        this.currentItem.maxLength = 1;
      } else if (val === types.REFERENCE) {
        this.currentItem.maxLength = 20;
      } else if (val === types.HEX) {
        this.currentItem.maxLength = 2048;
      } else {
        this.currentItem.maxLength = 255;
      }

      if (val !== types.REFERENCE) {
        this.currentItem.table = null;
      }

      if (val === types.DATE) {
        this.currentItem.formatter = "MM/dd/yyyy";
      } else if (val === types.TIME) {
        this.currentItem.formatter = "HH:mm:ss";
      } else if (val === types.DATETIME) {
        this.currentItem.formatter = "MM/dd/yyyy HH:mm:ss";
      }

      if (val === types.BOOLEAN) {
        this.currentItem.defaultValues = false;
      } else if (val === types.NUMBER) {
        this.currentItem.defaultValues = 0;
        this.currentItem.nullable = 0;
      } else {
        this.currentItem.defaultValues = "";
      }

      if (val === types.NUMBER) {
        this.$set(this.currentTab.form, this.currentItem.i, 0);
      } else if (val === types.BOOLEAN) {
        this.$set(this.currentTab.form, this.currentItem.i, false);
      } else {
        this.currentTab.form[this.currentItem.i] = "";
      }
      if (this.$refs.propertiesForm && this.$refs.propertiesForm[0]) {
        this.$nextTick(() => {
          this.$refs.propertiesForm[0].validate();
        });
      }
    },

    handleNumberInputChange(val, currentItem) {
      const value = val.replace(/[^-\d*\.\d+]/g, "");
      this.currentTab.form[currentItem.i] = value;
      this.currentItem.defaultValues = value;
    },
    handleHexInputChange(val, currentItem) {
      this.currentTab.form[currentItem.i] = val;
      this.currentItem.defaultValues = val;
      this.currentItem.defaultValues = this.currentItem.defaultValues.replace(
        /[^A-Fa-f0-9]/g,
        ""
      );
    },

    handleHexMaxLengthInput(val) {
      this.currentItem.maxLength = Number((val + "").replace(/[-.]/g, ""));
    },

    handleReferenceTableChange(val) {
      this.currentItem.defaultValues = [];
    },

    handleStringInput(val, currentItem) {
      this.currentTab.form[currentItem.i] = val;
      this.currentItem.defaultValues = val;
    },

    // 右侧label输入框输入值时的回调
    labelInputOnChange(value) {
      this.layout.find((item) => item.i === this.activeId).label = value;
    },

    defaultValueHandleChange(value) {
      console.log(value);
      this.layout.find((item) => item.i === this.activeId).defaultValue = value;
    },

    // 点击右侧ReferenceTable下拉框里面的按钮触发，新建该table的选项卡
    handleReferenceTableClick(id) {
      const tableId = id || this.currentItem.table.id;
      const data = this.tables[0].children.find((child) => child.id === tableId);
      this.newTab(data);
    },

    /**
     * @desc 新建table成功后的回调
     * @param tableName - 新建table的名称
     */
    handleNewTable(tableName) {
      // 重新获取数据
      this.fetchTemplateTableList().then((data) => {
        const newTable = data.find((_) => _.name === tableName);
        this.newTab(newTable);
      });
    },

    /**
     *  @desc 新增tab页
     *  @param data tab页的data
     *  @param openNewTab 新建后tab后是否直接跳到新tab
     */
    async newTab(data, openNewTab = true) {
      if (this.pushingData || this.layoutLoading) {
        return;
      }
      const index = this.tabs.findIndex((tab) => tab.id === data.id);
      this.$refs.tableTree.setCurrentKey(data.id);
      if (index === -1) {
        this.layoutLoading = true;
        this.pushingData = true;
        // 获取关联的表，用来回显
        const { layoutData, referenceTable } = await this.fetchTemplateTableLayoutList(
          data.id
        );
        this.layoutLoading = false;
        this.initTabData(data, layoutData, referenceTable, openNewTab);
        this.tabs = [...this.tabs];
      } else {
        if (openNewTab) {
          this.currentLayout = this.tabs[index].data;

          this.currentLayout.forEach((o) => {
            this.$set(o, "editable, false");
          });
          this.activeId = this.currentLayout.length > 0 ? this.currentLayout[0].i : "";
        }
        this.tabs[index].randomKey = Math.random();
        this.resizeContentHeight();
      }
      if (openNewTab) {
        this.currentTabId = data.id;
      }
      if (data.tableType === 3) {
        if (openNewTab) {
          this.page = "EntryList";
        }
      } else {
        this.page = "Design";
      }
    },

    // 保存指定的tab
    save(tabId) {
      if (!this.hasDesignPermission) {
        this.$message.error(`The user is unauthorized.`);
        return;
      }
      const forms = this.$refs.layoutForm;
      const form = forms.find((_) => _.$attrs.id === tabId);
      const tab = this.tabs.find((_) => _.id === tabId);
      let valid = true;
      if (this.tableType !== 3) {
        if (this.$refs.propertiesForm && this.$refs.propertiesForm.length > 0) {
          const propertiesForm = this.$refs.propertiesForm;

          propertiesForm[0].validate((_valid) => {
            valid = _valid;
          });
        }
      }

      form.validate(async (_valid) => {
        valid === _valid;
        if (valid) {
          const arr = [];
          populateTabData(arr, [tab], false);

          try {
            this.layoutLoading = true;
            await saveTemplateLayout(this.templateId, arr);
          } catch (err) {
            this.checkDuplicateKeys(tabId);
            this.layoutLoading = false;
            return this.$message.error(err);
          }

          this.$message.success(this.$t("common.message.operation_success"));
          const newQuery = JSON.parse(JSON.stringify(this.$route.query));
          delete newQuery.unsaved;
          this.$router.replace({ query: newQuery });

          try {
            const { layoutData } = await this.fetchTemplateTableLayoutList(tabId);
            layoutData.forEach((data) => {
              if (
                [types.DATE, types.DATETIME].includes(data.valueType) &&
                data.defaultValues
              ) {
                data.defaultValues = parseDate(data.defaultValues, data.formatter);
              }
            });
            const newItems = tab.data.filter((_) => _.new);

            if (newItems.length > 0) {
              newItems.forEach((_) => {
                // divder特殊处理
                if (_.type === 2) {
                  const newLayout = layoutData.find(({ x, y, w, h }) => {
                    return x === _.x && y === _.y && w === _.w && h === _.h;
                  });
                  tab.form[newLayout.i] = _.defaultValues;
                } else {
                  const newLayout = layoutData.find((data) => data.key === _.key);
                  this.$set(tab.form, newLayout.i, _.defaultValues);
                }
              });
            }

            this.$set(tab, "data", layoutData);
            this.$set(tab, "reserveData", deepClone(layoutData));
            this.$set(tab, "paramSchemaDesignChange", false);
            this.$set(tab, "reserveForm", Object.assign({}, tab.form));
            this.$set(tab, "positionChanged", false);
            this.$set(tab, "reserveJSON", JSON.stringify(layoutData.map(mapWatchData)));
            this.$set(
              tab,
              "reservePosition",
              JSON.stringify(
                layoutData.map((_) => ({
                  x: _.x,
                  y: _.y,
                  w: _.w,
                  h: _.h,
                }))
              )
            );
          } catch (err) {
            return this.$message.error(err);
          }

          if (tabId === this.currentTab.id) {
            this.currentLayout = this.currentTab.data;
            if (this.mode === 2) {
              this.currentLayout.push({
                x: this.currentLayout.length % 2,
                y: this.yPosition,
                i: "new",
                w: 1,
                h: 2,
              });
            }
          }
        }
      });
    },

    // 保存所有
    async saveAll() {
      if (!this.hasDesignPermission) {
        this.$message.error(`The user is unauthorized.`);
        return;
      }
      const tabs = this.tabs.filter((tab) => tab.tableType !== 3 && this.hasChanged(tab));
      if (tabs.length === 0) {
        return;
      }
      let valid = true;
      let forms = null;
      // const index = tabs.findIndex((tab) => tab.id === this.currentTabId)
      if (this.tableType !== 3) {
        if (this.$refs.propertiesForm && this.$refs.propertiesForm.length > 0) {
          const propertiesForm = this.$refs.propertiesForm;

          propertiesForm[0].validate((_valid) => {
            if (!_valid) {
              valid = false;
              return;
            }
          });
        }
      }

      forms = this.$refs.layoutForm;
      // 循环验证所有已打开的tab
      forms.forEach((form, index) => {
        form.validate((_valid) => {
          console.log(_valid);
          // 只要有不通过验证的tab，就跳到该tab页面
          if (!_valid) {
            valid = false;
            this.page = "Design";
            this.currentTabId = form.$attrs.id;
            this.currentLayout = this.tabs.find((tab) => tab.id === form.$attrs.id).data;
          }
        });
      });

      if (valid) {
        const arr = [];
        const triggerPublish = false;
        populateTabData(arr, tabs, triggerPublish);

        console.log(arr);

        if (tabs.length > 0) {
          try {
            this.layoutLoading = true;
            await saveTemplateLayout(this.templateId, arr);
            this.$message.success(this.$t("common.message.operation_success"));

            // 删除掉url中的unsaved参数，否则会一直有未保存的标志
            const newQuery = JSON.parse(JSON.stringify(this.$route.query));
            delete newQuery.unsaved;
            this.$router.replace({ query: newQuery });

            tabs.forEach(async (tab) => {
              const newItems = tab.data.filter((_) => _.new);
              const { layoutData } = await this.fetchTemplateTableLayoutList(tab.id);
              layoutData.forEach((data) => {
                if (
                  [types.DATE, types.DATETIME].includes(data.valueType) &&
                  data.defaultValues
                ) {
                  data.defaultValues = parseDate(data.defaultValues, data.formatter);
                }
              });
              if (newItems.length > 0) {
                newItems.forEach((_) => {
                  // divder特殊处理
                  if (_.type === 2) {
                    const newLayout = layoutData.find(({ x, y, w, h }) => {
                      return x === _.x && y === _.y && w === _.w && h === _.h;
                    });
                    tab.form[newLayout.i] = _.defaultValues;
                  } else {
                    const newLayout = layoutData.find((data) => data.key === _.key);
                    this.$set(tab.form, newLayout.i, _.defaultValues);
                  }
                });
              }
              this.$set(tab, "data", layoutData);
              this.$set(tab, "reserveData", deepClone(layoutData));
              this.$set(tab, "reserveForm", Object.assign({}, tab.form));
              this.$set(tab, "paramSchemaDesignChange", false);
              this.$set(tab, "positionChanged", false);
              this.$set(tab, "reserveJSON", JSON.stringify(layoutData.map(mapWatchData)));
              this.$set(
                tab,
                "reservePosition",
                JSON.stringify(
                  layoutData.map((_) => ({
                    x: _.x,
                    y: _.y,
                    w: _.w,
                    h: _.h,
                  }))
                )
              );
              this.currentLayout = this.currentTab.data;
              if (this.mode === 2) {
                this.currentLayout.push({
                  x: this.currentLayout.length % 2,
                  y: this.yPosition,
                  i: "new",
                  w: 1,
                  h: 2,
                });
              }
            });
          } catch (err) {
            this.checkDuplicateKeys();
            this.layoutLoading = false;
            this.$message.error(err.message);
          }
        }
      }
    },

    previewTemplate(fileType) {
      this.jsonDrawerVisible = true;
      this.previewDrawerLoading = true;
      this.previewDrawerFileType = fileType;
      getParameterTemplatePreview(this.templateId, fileType).then((data) => {
        this.previewDrawerLoading = false;
        if (fileType === 1) {
          const decoded = base64.decode(data.previewData) || "{}";
          this.jsonStr = JSON.parse(decoded);
        } else if (fileType === 2) {
          this.jsonStr = data.previewData;
          this.$refs.previewDrawer.decode(data.previewData);
        } else if (fileType === 3) {
          const xml = base64.decode(data.previewData);
          // const xml = formatXml(base64.decode(data.previewData))
          this.xml = formatXml(xml, {
            indentation: "  ",
            filter: (node) => node.type !== "Comment",
            collapseContent: true,
            lineSeparator: "\n",
          });
        }

        console.log(this.jsonStr);
      });
    },

    handleCommand(command) {
      if (command === "new") {
        this.newComponentDialogType = "save";
        this.newComponentVisible = true;
      } else if (command === "import/export") {
        this.componentImportVisible = true;
      }
    },

    handleComponentCommand(command, data) {
      if (command === "edit") {
        this.editCommonComponent(data);
      } else if (command === "remove") {
        this.removeCommonComponent(data);
      }
    },

    handlePreviewCommand(command) {
      if (command === "confPagePreview") {
        this.configPreviewDialogVisible = true;
        this.configurationPreviewKey = Math.random();
      } else if (command === "dataPreview") {
        this.previewTemplate(this.tables[0].fileType || 1);
      }
    },
    handleToolbarCommand(command) {
      if (!this.hasDesignPermission) {
        this.$message.error(`The user is unauthorized.`);
        return;
      }
      if (command === "import/export") {
        if (this.changed) {
          return this.$message.error(this.$t("flyparam.save_template"));
        }
        this.importExportVisible = true;
      } else if (command === "redesign") {
        this.redesignDialogVisible = true;
      } else if (command === "undo") {
        if (this.mode === 2) {
          this.undo(UNDO_CURRENT);
        } else {
          this.undoDialogVisible = true;
        }
      } else if (command === "batch") {
        this.$confirm(this.$t("flyparam.generate_batch"), this.$t("flyparam.warning"), {
          confirmButtonText: this.$t("common.confirm"),
          cancelButtonText: this.$t("common.cancel"),
          type: "warning",
        }).then(() => {
          runBatch(this.templateId).then(() => {
            this.$message.success(this.$t("common.operated_successfully"));
          });
        });
      }
    },

    undo(type) {
      // 重置所有tab的数据
      if (type === UNDO_ALL) {
        this.tabs.forEach((tab) => {
          this.$set(tab, "data", deepClone(tab.reserveData));
          this.$set(tab, "form", deepClone(tab.reserveForm));
          tab.paramSchemaDesignChange = false;
          tab.positionChanged = false;
        });
      } else if (type === UNDO_CURRENT) {
        // 重置当前tab的数据
        const tab = this.tabs.find((tab) => tab.id === this.currentTabId);
        this.$set(tab, "data", deepClone(tab.reserveData));
        this.$set(tab, "form", deepClone(tab.reserveForm));
        tab.paramSchemaDesignChange = false;
        tab.positionChanged = false;
      }

      // 简单模式下撤销之后要将新增按钮添加回去
      if (this.mode === 2) {
        this.tabs.forEach((_) => {
          _.data.push({
            h: 2,
            i: "new",
            w: 1,
            x: 0,
            y: 0,
          });
        });
      }

      // 移除校验结果
      const forms = this.$refs.layoutForm;
      forms.forEach((form) => form.clearValidate());

      console.log(this.tabs.find((tab) => tab.id === this.currentTabId).data);
      this.currentLayout = this.tabs.find((tab) => tab.id === this.currentTabId).data;
    },

    handleTableDropdownCommand(command, data) {
      if (command === "delete") {
        this.$commonConfirm({
          title: this.$t("flyparam.delete_table"),
          type: "danger",
          content: this.$t("flyparam.delete_table_warning", {
            name: data.name,
          }),
          btnSureTitle: this.$t("common.delete"),
          onConfirm: (instance, done) => {
            instance.loading = true;
            deleteTable(this.templateId, data.id)
              .then((res) => {
                this.$message.success(this.$t("page.delete_success"));
                // 选项卡中有被删除的table，则关闭该选项卡
                this.tabs = this.tabs.filter((tab) => tab.id !== data.id);
                // 如果被删除的table是当前打开的选项卡，还需要将当前选项卡切换到第一个
                if (this.currentTabId === data.id) {
                  this.currentTabId = this.tabs[0].id;
                  this.currentLayout = this.tabs[0].data;
                  this.activeId =
                    this.currentLayout.length > 0 ? this.currentLayout[0].i : "";
                  this.page = "Design";
                }
                done();
                this.fetchTemplateTableList();
              })
              .catch((e) =>
                this.$confirmError(e).then(() => {
                  done();
                })
              );
          },
        });
      } else if (command === "rename") {
        this.renameDialogVisible = true;
        this.renameId = data.id;
        this.renameName = data.name;
      }
    },

    handleEntryListOnClick() {
      this.page = "EntryList";
    },

    editCommonComponent(component) {
      this.newComponentDialogType = "update";
      this.newComponentVisible = true;
      this.comoponentDialogData = component;
    },

    removeCommonComponent(component) {
      this.$commonConfirm({
        title: this.$t("flyparam.delete_component"),
        type: "danger",
        content: this.$t("flyparam.delete_component_warning", {
          label: component.label,
        }),
        btnSureTitle: this.$t("common.delete"),
        onConfirm: (instance, done) => {
          instance.loading = true;
          deleteCommonComponent(component.componentId)
            .then((res) => {
              this.$message.success(this.$t("page.delete_success"));
              done();
              this.fetchCommonComponents();
            })
            .catch((e) =>
              this.$confirmError(e).then(() => {
                done();
              })
            );
        },
      });
    },

    handleFileUploadConfirm(filePath) {
      const item = this.currentLayout.find((item) => item.i === this.activeId);
      this.$set(item, "defaultValues", filePath);
      this.$set(this.currentTab.form, item.i, filePath);
    },

    handleSelectReference(item) {
      this.activeId = item.i;
      if (this.refTableType === 1) {
        this.layoutPreviewVisible = true;
      } else if (this.refTableType === 2) {
        this.activeId = item.i;
        if (this.currentItem.table) {
          this.selectReferenceVisible = true;
        } else {
          this.$message.error(this.$t("flyparam.select_table_warning"));
        }
      }
    },

    /**
     * 重新设计成功后的回调
     */
    handleRedesignConfirm() {
      if (this.paramTemplateId) {
        this.$emit("redesign");
      }
    },

    /**
     * File类型的参数点击选择文件
     */
    handleSelectFile(id) {
      this.activeId = id;
      this.uploadFileVisible = true;
    },

    // 右键点击参数弹出自定义菜单
    handleContextMenu(e, id) {
      this.activeId = id;
      const PopupMenuComp = Vue.extend(PopupMenu);

      const popupMenu = new PopupMenuComp({
        propsData: {
          x: e.clientX,
          y: e.clientY,
          id: id,
          tableList: this.tabs,
          parameterType: this.currentItem.valueType,
        },
      });

      popupMenu.$mount("#popup-menu");
      popupMenu.$on("close", () => {
        popupMenu.$destroy();
      });

      popupMenu.$on("copy", (tableId) => {
        this.handleCopy(tableId);
      });

      popupMenu.$on("delete", () => {
        this.removeItem(this.activeId);
      });
      return false;
    },

    handleCopy(tableId) {
      const obj = { ...this.currentItem };
      delete obj.paramId;
      delete obj.paramLayoutId;
      this.addItem({ ...obj, error: false, i: uuidv4() }, undefined, true, tableId);
    },

    // 模板自上次保存后是否发生过变化
    hasChanged(tab) {
      return (
        this.$route.query.unsaved || tab.paramSchemaDesignChange || tab.positionChanged
      );
    },

    handleGridItemMoved(i, newX, newY) {
      const item = this.currentLayout.find((item) => i === item.i);
      console.log(item);
      console.log(newX, newY);
    },

    drag: function (e, item) {
      const parentRect = document.getElementById("content").getBoundingClientRect();
      let mouseInGrid = false;
      if (
        mouseXY.x > parentRect.left &&
        mouseXY.x < parentRect.right &&
        mouseXY.y > parentRect.top &&
        mouseXY.y < parentRect.bottom
      ) {
        mouseInGrid = true;
      }
      if (
        mouseInGrid === true &&
        this.currentTab.data.findIndex((item) => item.i === "drop") === -1
      ) {
        this.currentTab.data.push({
          x: (this.currentTab.data.length * 2) % (this.colNumber || 12),
          y: this.currentTab.data.length + (this.colNumber || 12),
          w: 1,
          h: 2,
          i: "drop",
        });
      }
      const index = this.currentTab.data.findIndex((item) => item.i === "drop");
      if (index !== -1) {
        try {
          const children = this.$refs.gridlayout[0].$children;
          if (children[this.currentTab.data.length]) {
            this.$refs.gridlayout[0].$children[
              this.currentTab.data.length
            ].$refs.item.style.display = "none";
          }
        } catch (err) {
          console.error(err);
          console.log(this.currentTab.data.length);
          console.log(this.$refs.gridlayout[0].$children[this.currentTab.data.length]);
        }
        const el = this.$refs.gridlayout[0].$children[index];
        el.dragging = {
          top: mouseXY.y - parentRect.top,
          left: mouseXY.x - parentRect.left,
        };
        const new_pos = el.calcXY(
          mouseXY.y - parentRect.top,
          mouseXY.x - parentRect.left
        );
        if (mouseInGrid === true) {
          this.$refs.gridlayout[0].dragEvent(
            "dragstart",
            "drop",
            new_pos.x,
            new_pos.y,
            2,
            1
          );
          DragPos.x = this.currentTab.data[index].x;
          DragPos.y = this.currentTab.data[index].y;
        }
        if (mouseInGrid === false) {
          this.$refs.gridlayout[0].dragEvent(
            "dragend",
            "drop",
            new_pos.x,
            new_pos.y,
            2,
            1
          );
          this.currentTab.data = this.currentTab.data.filter((obj) => obj.i !== "drop");
        }
      }
    },
    dragend: function (e, item) {
      const parentRect = document.getElementById("content").getBoundingClientRect();
      let mouseInGrid = false;
      if (
        mouseXY.x > parentRect.left &&
        mouseXY.x < parentRect.right &&
        mouseXY.y > parentRect.top &&
        mouseXY.y < parentRect.bottom
      ) {
        mouseInGrid = true;
      }
      if (mouseInGrid === true) {
        this.$refs.gridlayout[0].dragEvent("dragend", "drop", DragPos.x, DragPos.y, 2, 1);
        this.currentTab.data = this.currentTab.data.filter((obj) => obj.i !== "drop");

        this.addItem(item);

        this.currentLayout = this.currentLayout.filter((obj) => obj.i !== "drop");

        this.currentTab.data.push({
          x: DragPos.x,
          y: DragPos.y,
          w: 1,
          h: 2,
          ...item,
        });
        this.$refs.gridlayout[0].dragEvent(
          "dragend",
          DragPos.i,
          DragPos.x,
          DragPos.y,
          2,
          1
        );
        try {
          this.$refs.gridlayout[0].$children[
            this.currentLayout.length
          ].$refs.item.style.display = "block";
        } catch {}
      }
    },
    checkDuplicateKeys(tabId) {
      let duplicateKey = [];
      let activeTabId = "";
      let activeTab = null;
      const findDuplicateKeys = (tab) => {
        const arr = tab.data.map((item) => item.key);

        const findDuplicates = (arr) =>
          arr.filter((item, index) => arr.indexOf(item) !== index);

        duplicateKey = [...new Set(findDuplicates(arr))];

        for (const item of tab.data) {
          this.$set(item, "error", false);
          if (duplicateKey.includes(item.key)) {
            item.error = true;
            activeTabId = tab.id;
            activeTab = tab;
          }
        }
      };
      // 指定某个搜索某个tab的重复key
      if (tabId) {
        const tab = this.tabs.find((obj) => obj.id === tabId);
        findDuplicateKeys(tab);
      } else {
        // 搜索所有tab
        this.tabs.forEach((tab) => {
          findDuplicateKeys(tab);
        });
      }

      if (duplicateKey.length > 0) {
        this.currentTabId = activeTabId;
        this.currentLayout = activeTab.data;
        this.activeId = this.currentLayout.length > 0 ? this.currentLayout[0].i : "";
      }
    },
    handleParameterClick(item) {
      this.activeId = item.i;
      if (item.error) {
        item.error = false;
      }
    },
  },
};
</script>

<style lang="scss">
.flyparameter {
  background-color: #edf2f9;
  min-height: calc(100vh - 60px);

  .button-wrapper {
    width: 24px;
    height: 24px;
    line-height: 22px;
    border-radius: 4px;
    background-color: transparent;
    color: #676879;
    transition-delay: 0s, 0s;
    transition-duration: 0.07s, 0.1s;
    transition-property: transform, background-color;
    transition-timing-function: ease, cubic-bezier(0.4, 0, 0.2, 1);
    i {
      width: 24px;
      height: 100%;
    }
    &:not(.button-wrapper--disabled) {
      &:hover {
        background-color: #dcdfec;
      }
      &:active {
        transform: scale(0.95) translate3d(0, 0, 0);
      }
    }
    &--disabled {
      cursor: not-allowed;
    }
  }

  .sidebar-table .el-tree .el-icon-caret-right {
    display: none;
  }

  .el-tabs--border-card > .el-tabs__content {
    padding: 5px;
  }

  .el-tabs__nav-wrap::after {
    /* display: none; */
  }

  .sidebar .sidebar-components {
    .components-dropdown.el-dropdown {
      position: absolute;
      top: 10px;
      right: 13px;
      z-index: 2;
    }

    .components-menu {
      font-size: 24px;
      cursor: pointer;
      z-index: 2;

      &:hover {
        color: rgba(64, 158, 255, var(--tw-text-opacity));
      }
    }

    .el-tabs__content {
      margin-top: 10px;
    }
  }

  .main-content {
    .el-card__body {
      padding: 0;
    }

    .el-tabs__new-tab {
      display: none;
    }

    .main-content__right {
      .el-form-item {
        margin-bottom: 4px;

        .el-select {
          display: block;
        }
      }

      .el-form-item__label {
        float: none;
      }
    }
  }
}
</style>

<style lang="scss" scoped>
$activeColor: #409eff;
// $buttonHoverBgColor: rgba(31,35,41, 0.1);
$buttonHoverBgColor: #ecf5ff;
@import "@/styles/mixin.scss";
@include parameterCard;

::v-deep .sidebar-components .el-tabs__content {
  // max-height: 220px;
  overflow-y: auto;
}

.flyparameter::v-deep .el-form-item.form-item__string.is-error .el-form-item__error {
  /* transform: translateY(-6px); */
}

::v-deep .vue-grid-item > .vue-resizable-handle {
  padding: 0;
}

::v-deep .sidebar-components .el-tabs--top .el-tabs__item.is-top:nth-child(2) {
  padding-left: 20px;
}

::v-deep .main-content .el-tabs__nav-wrap {
  width: 70%;

  .el-tabs__nav-prev {
    left: 5px;
  }
}

.param-properties {
  @include elSelectAppend;
}

.toolbar--right {
  margin-right: 20px;

  .el-dropdown-link {
    border-radius: 4px;
    padding: 5px;
    &:hover {
      background-color: $buttonHoverBgColor;
    }
  }

  .el-icon-s-tools {
    font-size: 18px;
    vertical-align: -2px;
  }
}

.save-dropdown {
  border: 1px solid transparent;
  margin-right: 10px;
  &:hover {
    border-color: #dcdfe6;
  }

  &::v-deep {
    .el-button {
      border: 0;
      padding: 2px 10px;
      &:hover {
        border-color: #dcdfe6;
      }
      &.el-dropdown__caret-button {
        padding: 2px 0;
        height: 22px;
        &::before {
          display: none;
        }
      }
    }
  }
}

.flyparameter {
  #content {
    // height: calc(100vh - 180px);
    overflow-y: auto;
    overflow-x: hidden;

    ::v-deep .el-form-item__content {
      height: 100%;
    }
  }

  .tree-node-active {
    color: $activeColor;
  }

  .icon-plus {
    float: right;
    font-weight: bold;
    @apply text-secondary hover:text-element-blue transition-colors;
    cursor: pointer;
  }

  .flyparameter-design-row {
    min-height: calc(100vh - 104px);
  }

  .param-button-group {
    position: absolute;
    z-index: 1;
    right: 6px;
    top: 6px;

    button {
      &.active {
        background-color: #ffffff;
        color: #409eff;
        border: 1px solid transparent;
        border-right-color: #dcdfe6;
        border-left-color: #dcdfe6;

        &:last-child {
          margin-right: -1px;
        }
      }

      background-color: #f5f7fa;
      color: #767676;
      padding: 10px 20px;
      font-weight: bold;
    }
  }

  .toolbar {
    .toolbar-icon {
      cursor: pointer;
    }

    .icon-save {
      color: #409eff;
      &.disabled {
        cursor: not-allowed;
        color: #000000;
        opacity: 0.2;
      }
    }

    .icon-eye {
      color: #05cd2d;
    }

    .dropdown-container {
      display: flex;
      align-items: center;
    }
  }

  .sidebar {
    height: calc(100vh - 104px);

    .sidbar-table,
    .sidebar-components {
      height: 50%;
    }

    .sidebar-body {
      height: calc(100% - 42.5px);
    }

    .sidebar-table,
    .sidebar-components {
      background-color: #ffffff;

      .sidebar-title {
        padding: 10px;
        color: #9e9e9e;
        background-color: #f5f7fa;
      }

      .basic-components {
        color: #606266;

        li {
          display: flex;
          align-items: center;
          justify-content: space-between;
          height: 26px;
          padding: 0 12px 0 8px;

          span {
            user-select: none;
          }
          &:hover {
            background-color: #f5f7fa;
          }

          .basic-component__label {
            @apply flex items-center;
            i,
            img {
              @apply mr-2;
            }
          }

          .icon-plus {
            float: right;
            font-weight: bold;
            @apply text-secondary hover:text-element-blue transition-colors;
            cursor: pointer;
          }
        }
      }
    }

    .sidebar-table {
      // min-height: 200px;
      .sidebar-body {
      }
    }

    .sidebar-components {
      //height: calc(100vh - 310px);
    }

    .sidebar-body {
      padding: 5px;

      overflow: auto;
      position: relative;
    }
  }

  .main-content {
    // padding: 10px;
    padding: 5px;

    .main-content__left {
      /* padding: 20px; */
      border-right: 1px solid #edf2f9;
    }

    .param-properties__title {
      padding: 10px;
      color: #9e9e9e;
      /* background-color: #f5f7fa; */
    }

    .main-content__right {
      padding: 0px 20px 20px;
      min-height: 487px;
    }
  }

  .member-avatar {
    height: 45px;
    width: 45px;
    border-radius: 50%;
    margin: 0 4px 4px 0;
    user-select: none;
    object-fit: cover;
  }

  .new-member {
    background-color: #091e420a;
    border-radius: 100%;
    height: 45px;
    width: 45px;
    margin: 0 4px 4px 0;
    .icon-add {
      font-size: 16px;
      height: 22.5px;
      line-height: 22.5px;
      padding: 8px;
      width: 22.5px;
    }
  }

  .disabled {
    color: rgba(0, 0, 0, 0.2);
  }

  @include elSelectAppend;
}
</style>
